import tkinter as tk
from tkinter import ttk
from tkinter import scrolledtext
import tkinter.messagebox
from tkinter import END
import tkinter.filedialog
import traceback
import ctypes
import inspect
import queue

#import asyncio
#import concurrent
import threading
import time

from rgblink_cmd import *
from process_cmd import *

import serial
import serial.tools.list_ports
import sys
import os



# 把可执行文件本目录下的文件可以import
sys.path.append(os.path.dirname(os.path.realpath(sys.argv[0])))

plist = list(serial.tools.list_ports.comports())
comFd = None
exitReceiveRs232 = None
comNameList = [];
rs232_queue = queue.Queue()  # 用于接收串口数据的FIFO队列

for plist_0 in plist:
        #print(plist_0[0])
        comNameList.append(plist_0[0])
        
#print(comNameList)
comNameList.sort()
if len(comNameList) == 0:
	comNameList.append("COM1")
print(comNameList)

winMain = tk.Tk()
winMain.title("Python GUI")

win = ttk.Frame(winMain)
win.grid(column=0, row=0)
#win.grid_columnconfigure(0, weight=1)

winExtend = ttk.Frame(winMain)
winExtend.grid(column=2, row=0)

# separator
verSeparator = ttk.Separator(winMain, orient="vertical")
verSeparator.grid(row = 0, column=1, sticky="nswe", pady=10, padx=10)

def clickOpenButton():
    global comFd
    if openButton['text'] == "Open":
        
        if comFd:
            comFd.close()
        
        try:
            comFd = serial.Serial(comNameList[comChosen.current()], 115200,timeout = 3)
        except serial.serialutil.SerialException as err:
            print(err)
            message = comNameList[comChosen.current()] + ' 被其他程序占用，无法打开'
            tkinter.messagebox.showerror('错误',  message)
        else:
            openButton.configure(text="Close")
            comChosen.configure(state='disabled')
        
        
                
        print("open ", comNameList[comChosen.current()])
    else:
        openButton.configure(text="Open")
        comChosen.configure(state='enabled')
        
        if comFd:
            comFd.close()
            comFd = None
        print("close ", comNameList[comChosen.current()])
    
# open button	
openButton = ttk.Button(win, text="Open", command=clickOpenButton)
openButton.grid(column=0, row=0)

# com select
com = tk.StringVar()

comChosen = ttk.Combobox(win, width=6, textvariable=com, state='readonly')
comChosen['values'] = comNameList
comChosen.grid(column=1, row=0)
comChosen.current(0)

# baudrate select
baudrate = tk.StringVar()
baudrateChosen = ttk.Combobox(win, width=6, textvariable=baudrate, state='readonly')
baudrateChosen['values'] = ('115200', '57600', '38400', '19200', '9600')
baudrateChosen.grid(column=2, row=0)
baudrateChosen.current(0)

# check bit
checkBit = tk.StringVar()
checkBitChosen = ttk.Combobox(win, width=6, textvariable=checkBit, state='readonly')
checkBitChosen['values'] = ('None', 'Odd', 'Even')
checkBitChosen.grid(column=3, row=0)
checkBitChosen.current(0)

# bit width
bitWidth = tk.StringVar()
bitWidthChosen = ttk.Combobox(win, width=2, textvariable=bitWidth, state='readonly')
bitWidthChosen['values'] = ('8')
bitWidthChosen.grid(column=4, row=0)
bitWidthChosen.current(0)

# stop bit
stopBit = tk.StringVar()
stopBitChosen = ttk.Combobox(win, width=2, textvariable=stopBit, state='readonly')
stopBitChosen['values'] = ('1', '2')
stopBitChosen.grid(column=5, row=0)
stopBitChosen.current(0)

# ascii or hex
displayMode = tk.StringVar()
displayModeChosen = ttk.Combobox(win, width=6, textvariable=displayMode, state='readonly')
displayModeChosen['values'] = ('ASCII', 'HEX')
displayModeChosen.grid(column=6, row=0)
displayModeChosen.current(0)

# clear display
def clickClearButton():
	displayScr.delete(1.0, END)
	
# clear button	
clearButton = ttk.Button(win, text="Clear", command=clickClearButton)
clearButton.grid(column=7, row=0)

# display text
scrolW = 80
scrolH = 8
displayScr = scrolledtext.ScrolledText(win, width=scrolW, height=scrolH, wrap=tk.WORD)
displayScr.grid(column=0, columnspan=8, row=1)


    
# # receive rs232
def receive_rs232():
    global comFd
    global exitReceiveRs232
    global rs232_queue
    while 1:
        if comFd:
            try:
                n = comFd.inWaiting()
            except:
                None
            else:
                if n:
                    receiveBytes = comFd.read(n)
                    interpret_cmd(receiveBytes)
                    rs232_queue.put(receiveBytes)
                    if displayModeChosen.current() == 0:                
                        displayScr.insert("insert", receiveBytes)
                    else:
                        displayScr.insert("insert", receiveBytes.hex())
                    displayScr.see(END)
        time.sleep(0.1)
        if exitReceiveRs232:
            return

# # p = Process(target=receive_rs232, args=(displayScr, ))
# # p.start()
# # p.join()
            
# separator
sendSeparator = ttk.Separator(win, orient="horizontal")
sendSeparator.grid(row = 2, columnspan=8, sticky="nswe", pady=10)

# send button
def clickSendButton():
    global comFd
    if(sendModeChosen.get() == "ASCII"):
        print("Send ASCII")        
        if comFd:
            #print(sendScr.get("1.0", 'end-1c'))
            comFd.write(sendScr.get("1.0", 'end-1c').encode(encoding="ascii"))
    else:
        print("Send HEX")
        if comFd:
            hexStr = sendScr.get("1.0", 'end-1c')
            comFd.write(bytes.fromhex(hexStr))
    
    
sendButton = ttk.Button(win, text="Send", command=clickSendButton)
sendButton.grid(column=0, row=3)


# send ascii or hex select
sendMode = tk.StringVar()
sendModeChosen = ttk.Combobox(win, width=6, textvariable=sendMode, state='readonly')
sendModeChosen['values'] = ('ASCII', 'HEX')
sendModeChosen.grid(column=1, row=3)
sendModeChosen.current(0)

# auto time
everyTime = tk.StringVar()
everyTime.set("1000")
everyTimeEntered = ttk.Entry(win, width=5, textvariable=everyTime, )
everyTimeEntered.grid(column=4, row=3,sticky = 'e')

# label ms
msLabel = tk.Label(win, text="ms", width=5, anchor = 'w')
msLabel.grid(column=5, row=3)
#msLabel.place_configure(anchor="center")

# auto check button
autoEnable = tk.IntVar()
autoCheckButton = tk.Checkbutton(win, text="Auto", variable=autoEnable)
autoCheckButton.grid(column=3, row=3)

# send text
sendScr = scrolledtext.ScrolledText(win, width=80, height=8, wrap=tk.WORD)
sendScr.grid(column=0, columnspan=8, row=4)

# send file button
def clickSendFileButton():
	print("send file " + sendFileNameEntered.get())
	
sendFileButton = ttk.Button(win, text="Send File", command=clickSendFileButton)
sendFileButton.grid(column=0, row=5)

# send file name
sendFileName = tk.StringVar()
sendFileNameEntered = ttk.Entry(win, width=55, textvariable=sendFileName, )
sendFileNameEntered.grid(column=1, row=5, columnspan=6)

# open file button
def clickOpenFileButton():
	print("open file ")
	
openFileButton = ttk.Button(win, text="Open File", command=clickOpenFileButton)
openFileButton.grid(column=7, row=5)

# send cmd frame
sendCmdFrame = ttk.Frame(win)
sendCmdFrame.grid(column=0, row=6, columnspan=8, sticky='w', padx=4)

# send cmd button
def clickSendCmdButton():
    global comFd
    print("send cmd ")
    sendScr.delete(1.0, END)
    cmd_temp = rgblink_cmd(sn=0, adr=int(adr.get(), 16), cmd=int(cmd.get(), 16), 
        data1 = int(dat1.get(), 16), data2=int(dat2.get(), 16), 
        data3 = int(dat3.get(), 16), data4=int(dat4.get(), 16))
    sendScr.insert("insert", cmd_temp.toSendString())
    if comFd:
        comFd.write(cmd_temp.toSendBytes())
    
sendCmdButton = ttk.Button(sendCmdFrame, text="Send Cmd", command=clickSendCmdButton)
sendCmdButton.grid(column=0, row=0, sticky='W')

# label adr
adrLabel = tk.Label(sendCmdFrame, text="adr", width=5)
adrLabel.grid(column=1, row=0)

# entry adr
adr = tk.StringVar()
adr.set("00")
adrEntered = ttk.Entry(sendCmdFrame, width=4, textvariable=adr, )
adrEntered.grid(column=2, row=0)

# label cmd
cmdLabel = tk.Label(sendCmdFrame, text="cmd", width=5)
cmdLabel.grid(column=3, row=0)

# entry cmd
cmd = tk.StringVar()
cmd.set("00")
cmdEntered = ttk.Entry(sendCmdFrame, width=4, textvariable=cmd, )
cmdEntered.grid(column=4, row=0)

# label dat1
dat1Label = tk.Label(sendCmdFrame, text="dat1", width=5)
dat1Label.grid(column=5, row=0)

# entry dat1
dat1 = tk.StringVar()
dat1.set("00")
dat1Entered = ttk.Entry(sendCmdFrame, width=4, textvariable=dat1, )
dat1Entered.grid(column=6, row=0)

# label dat2
dat2Label = tk.Label(sendCmdFrame, text="dat2", width=5)
dat2Label.grid(column=7, row=0)

# entry dat2
dat2 = tk.StringVar()
dat2.set("00")
dat2Entered = ttk.Entry(sendCmdFrame, width=4, textvariable=dat2, )
dat2Entered.grid(column=8, row=0)

# label dat3
dat3Label = tk.Label(sendCmdFrame, text="dat3", width=5)
dat3Label.grid(column=9, row=0)

# entry dat3
dat3 = tk.StringVar()
dat3.set("00")
dat3Entered = ttk.Entry(sendCmdFrame, width=4, textvariable=dat3, )
dat3Entered.grid(column=10, row=0)

# label dat4
dat4Label = tk.Label(sendCmdFrame, text="dat4", width=5)
dat4Label.grid(column=11, row=0)

# entry dat4
dat4 = tk.StringVar()
dat4.set("00")
dat4Entered = ttk.Entry(sendCmdFrame, width=4, textvariable=dat4, )
dat4Entered.grid(column=12, row=0)

# separator
endSeparator = ttk.Separator(win, orient="horizontal")
endSeparator.grid(row = 7, columnspan=8, sticky="nswe", pady=10)

#action.configure(state='disable')

# extend win
# tab control
tabControl = ttk.Notebook(winExtend)
tabList = []
for i in range(0, 16):
        tabList += [ttk.Frame(tabControl)]
#print("tabList len = ", len(tabList))

for i in range(0, len(tabList)):
        tabControl.add(tabList[i], text="run{:2d}".format(i))
        
tabControl.grid(column=0, row=0, sticky='W')

runTextList = []
for i in range(0, len(tabList)):
        scrolW = 90
        scrolH = 20
        runText = scrolledtext.ScrolledText(tabList[i], width=scrolW, height=scrolH, wrap=tk.WORD)
        runText.grid(row=0, column=0)
        runTextList += [runText]




# button bar
buttonBar = ttk.Frame(winExtend)
buttonBar.grid(column=0, row=1, sticky='W')

# run button
runThread = None
def clickRunButton():
    global comFd
    global runThread
    print("run button")
    runButton.configure(state='disabled')
    #exec(runText.get("1.0", 'end-1c'))
    runThread = threading.Thread(target=runTextThread, name='run', daemon=True)
    runThread.start()

def getCurrentRunText():
    global tabControl
    index = tabControl.index(tabControl.select())
    currentText = runTextList[index]
    return currentText
    
def runTextThread():
    global runThread
    
    currentText = getCurrentRunText()
    
    try:
        exec(currentText.get("1.0", 'end-1c'))
    except Exception as info:
        traceback.print_exc()        
    finally:
        runButton.configure(state='enabled')
        runThread = None
    
    
runButton = ttk.Button(buttonBar, text="Run", command=clickRunButton)
runButton.grid(column=0, row=0, sticky='W')

# stop button
def _async_raise(tid, exctype):  
    """raises the exception, performs cleanup if needed"""  
    tid = ctypes.c_long(tid)  
    if not inspect.isclass(exctype):  
        exctype = type(exctype)  
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))  
    if res == 0:  
        raise ValueError("invalid thread id")  
    elif res != 1:  
        # """if it returns a number greater than one, you're in trouble,  
        # and you should call it again with exc=NULL to revert the effect"""  
        ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)  
        raise SystemError("PyThreadState_SetAsyncExc failed")

def stop_thread(thread):  
    _async_raise(thread.ident, SystemExit)

def clickStopButton():
    global runThread
    print("stop button")   
    if runThread:
        stop_thread(runThread)
        runThread = None

stopButton = ttk.Button(buttonBar, text="Stop", command=clickStopButton)
stopButton.grid(column=1, row=0, sticky='W')
        
# save button
def clickSaveButton():
    fileName = tkinter.filedialog.asksaveasfilename(filetypes=[("txt格式","txt")])
    print("save ", fileName)
    if fileName:
        currentText = getCurrentRunText()
        with open(fileName, 'w') as file:
            file.write(currentText.get("1.0", 'end-1c'))
            file.close()
    
saveButton = ttk.Button(buttonBar, text="Save", command=clickSaveButton)
saveButton.grid(column=2, row=0,)

# load button
def clickLoadButton():
    fileName = tkinter.filedialog.askopenfilename(filetypes=[("txt格式","txt")])
    print("load ", fileName)
    if fileName:
        currentText = getCurrentRunText()
        with open(fileName, 'r') as file:
            temp = file.read()
            file.close()
            currentText.delete(1.0, END)
            currentText.insert("insert", temp)
            currentText.see(END)
    
loadButton = ttk.Button(buttonBar, text="Load", command=clickLoadButton)
loadButton.grid(column=3, row=0,)

sendSn = 0

def getDataBlockCheckSum(dataBlock):
    checkSum = 0
    for i in range(0, len(dataBlock)):
        checkSum = (checkSum+dataBlock[i])
    checkSum = checkSum % 256
    return checkSum
    
# send cmd function
def SendCmd(adr = 0, cmd = 0, dat1 = 0, dat2 = 0, dat3 = 0, dat4 = 0, dataBlock=bytes()):
    global comFd
    global sendSn
    sendSn = sendSn+1
    sendSn = sendSn % 256
    dataLen = len(dataBlock)
    if(dataLen>0):
        dat3 = (dataLen+1) % 256
        dat4 = int((dataLen+1) / 256) % 256
    tempcmd = rgblink_cmd(adr=adr, sn=sendSn, cmd=cmd, data1=dat1, data2=dat2, data3=dat3, data4=dat4)
    sendBytes = tempcmd.toSendBytes()
    if(dataLen>0):
        sendBytes = sendBytes+dataBlock+getDataBlockCheckSum(dataBlock).to_bytes(1, byteorder='little')
    
    if comFd:
        comFd.write(sendBytes)
    return sendSn

device_set.sendCmd=SendCmd
# send string function
def SendStr(str=""):
    global comFd
    if comFd:
        comFd.write(str.encode(encoding="ascii"))

# send bytes function
def SendByte(bytes=b''):
    global comFd
    if comFd:
        comFd.write(bytes)

# send hex string function
def SendHex(hexStr=""):
    global comFd
    if comFd:
        temp = bytes.fromhex(hexStr)
        comFd.write(temp)
 
# power on
def PowerOn():
    SendCmd(cmd=0x68, dat1=0x10, dat2=1)
    
# power off
def PowerOff():
    SendCmd(cmd=0x68, dat1=0x10, dat2=0)

# take
def Take():
	SendCmd(cmd=0x78, dat1=0x00, dat2=0)

# 读取串口接收到的数据
def GetRs232(timeout=0):
        receive_bytes = bytes()
        while (rs232_queue.empty() == False):
                receive_bytes += rs232_queue.get(timeout=0)
        if (timeout != 0):
                try:
                        while 1:
                                receive_bytes += rs232_queue.get(timeout=timeout)
                except queue.Empty as e:
                        return receive_bytes
                
        return receive_bytes
def GetReceiveBuffer(timeout=0):
        return GetRs232(timeout)

powerOnButton = ttk.Button(buttonBar, text="Power On", command=PowerOn)
powerOnButton.grid(column=4, row=0, sticky='W')
powerOffButton = ttk.Button(buttonBar, text="Power Off", command=PowerOff)
powerOffButton.grid(column=5, row=0, sticky='W')
takeButton = ttk.Button(buttonBar, text="Take", command=Take)
takeButton.grid(column=6, row=0, sticky='W')
	  
# load run text

for i in range(0, len(tabList)):        
        try:
            file = open('run{}.txt'.format(i+1), 'r')
        except:
            None        
        else: 
            temp = file.read()
            runTextList[i].insert("insert", temp)
            runTextList[i].see(END)
            file.close()


winMain.resizable(1, 1)

def callbackDestroy():
    print("destroy")
    # save run text
    for i in range(0, len(tabList)):            
            with open('run{}.txt'.format(i+1), 'w') as file:
                file.write(runTextList[i].get("1.0", 'end-1c'))
                file.close()
    winMain.destroy()

winMain.protocol("WM_DELETE_WINDOW", callbackDestroy)
    
t = threading.Thread(target=receive_rs232, name='worker')
t.start()
winMain.mainloop()
exitReceiveRs232 = 1
if runThread:
    runThread.join(0.1)

